// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

function arrayBufferFromByteList(byteList) {
  return (new Uint8Array(byteList)).buffer;
}

function byteListFromArrayBuffer(arrayBuffer) {
  return Array.from(new Uint8Array(arrayBuffer));
}

// Returns the list of certificateProvider ClientCertificateInfo instances,
// given the parsed JSON value received from the C++ handler.
function clientCertificateInfoListFromJson(parsedCertInfoList) {
  return parsedCertInfoList.map(parsedCertInfo => {
    const certInfo = Object.assign({}, parsedCertInfo);
    certInfo.certificateChain[0] =
        arrayBufferFromByteList(parsedCertInfo.certificateChain[0]);
    return certInfo;
  });
}

// Transforms the certificateProvider signature request instance into a
// JSON-ifiable value that may be sent to the C++ handler.
function jsonifiableFromSignRequest(signRequest) {
  const transformedSignRequest = Object.assign({}, signRequest);
  transformedSignRequest.input = byteListFromArrayBuffer(signRequest.input);
  transformedSignRequest.certificate =
      byteListFromArrayBuffer(signRequest.certificate);
  return transformedSignRequest;
}

// Listener for the chrome.certificateProvider.onCertificatesUpdateRequested
// event.
function onCertificatesUpdateRequested(request) {
  // Request certificates from the C++ side.
  chrome.test.sendMessage(
      JSON.stringify(['getCertificates']),
      onCertificatesResponseFromCpp.bind(null, request.certificatesRequestId));
}

// Calls chrome.certificateProvider.setCertificates with the given certificates.
function onCertificatesResponseFromCpp(certificatesRequestId, response) {
  const certInfoList = clientCertificateInfoListFromJson(JSON.parse(response));
  let details = {clientCertificates: certInfoList};
  if (certificatesRequestId !== null)
    details.certificatesRequestId = certificatesRequestId;

  chrome.certificateProvider.setCertificates(details);
}

// Listener for the chrome.certificateProvider.onSignatureRequested event.
function onSignatureRequested(request) {
  requestSignatureFromCpp(
      request, /* pinStatus= */ 'not_requested', /* pin= */ '');
}

function requestSignatureFromCpp(signatureRequest, pinStatus, pin) {
  chrome.test.sendMessage(
      JSON.stringify([
        'onSignatureRequested', jsonifiableFromSignRequest(signatureRequest),
        pinStatus, pin
      ]),
      onSignatureResponseFromCpp.bind(null, signatureRequest));
}

function onSignatureResponseFromCpp(signatureRequest, response) {
  const parsedResponse = JSON.parse(response);
  if (parsedResponse === null) {
    // The C++ handler signaled an error.
    const details = {
      signRequestId: signatureRequest.signRequestId,
      error: 'GENERAL_ERROR'
    };
    chrome.certificateProvider.reportSignature(details);
    return;
  }
  if (parsedResponse.stopPinRequest) {
    // The C++ handler asked to stop the PIN request.
    chrome.certificateProvider.stopPinRequest(
        parsedResponse.stopPinRequest, function() {});
    // Note that we're not returning here, since the parsed response may contain
    // the signature as well.
  }
  if (parsedResponse.signature) {
    // Forward the signature generated by the C++ handler.
    const details = {
      signRequestId: signatureRequest.signRequestId,
      signature: arrayBufferFromByteList(parsedResponse.signature)
    };
    chrome.certificateProvider.reportSignature(details);
  }
  if (parsedResponse.requestPin) {
    // The C++ handler asked to request the PIN. After the PIN is obtained,
    // we'll request the signature from the C++ handler again.
    chrome.certificateProvider.requestPin(
        parsedResponse.requestPin, requestPinResponse => {
          const pin = (requestPinResponse && requestPinResponse.userInput) ?
              requestPinResponse.userInput :
              '';
          const pinStatus = chrome.runtime.lastError ?
              ('failed:' + chrome.runtime.lastError) :
              (pin ? 'ok' : 'canceled');
          requestSignatureFromCpp(signatureRequest, pinStatus, pin);
        });
  }
}

chrome.certificateProvider.onCertificatesUpdateRequested.addListener(
    onCertificatesUpdateRequested);

chrome.certificateProvider.onSignatureRequested.addListener(
    onSignatureRequested);

// Listen for messages that are 'setCertificates'.
// When receiving one, call chrome.certificateProvider.setCertificates().
chrome.test.onMessage.addListener((msg) => {
  if (msg.name !== 'setCertificates')
    return;

  chrome.certificateProvider.setCertificates({
    clientCertificates:
        clientCertificateInfoListFromJson(msg.certificateInfoList)
  });
});
